package mkv.MyGUI;
import processing.core.*;
import java.awt.event.KeyEvent;

/**
 * An interface for standardising the features of MyGUI components.
 * <p>
 * MyGUIObject is an interface for defining Graphical User Interface (GUI) objects for
 * Processing that are designed to work with the MyGUI collection class. Feel free t
 * extend this with your own GUI elements and send them to me for inclusion within the
 * library.
 * </p>
 * <p>
 * The MyGUIObject contains a standard set of variables and methods
 * which should be common amongst all MyGUIObjects. All interface objects, including
 * MyGUI stem from this class, it is at the top of the MyGUI hierachy. This change was
 * made so that any MyGUIObject could be the _parent of another MyGUIObject which was
 * important when developing MyGUIGroup.
 * </p>
 * <p>
 * The methods and variables defined below are meant to be useful and straight forward to
 * use.
 * </p>
 * 
 * @author John Beech
 */
public abstract class MyGUIObject {
    /**
     * A direct reference to the main processing PApplet. All drawing methods will be sent
     * to to this object.
     */
    public PApplet _root;
    /**
     * The MyGUIObject which acts as the parent of this object. Style, mouse coordinates,
     * visibility and many other variables are inherited from a parent. Parents are not
     * defined in the constructor, instead they must be set using the setParent() method.
     * @see #setParent(MyGUIObject parent)
     */
    public MyGUIObject _parent;
    /**
     * A reference to a MyGUIStyle object used in draw or for font metrics.
     */
    public MyGUIStyle _style;
    /**
     * Positional coordinate of the object. The actual position will depend on the
     * coordinates of parent objects. Parent objects can be translated, scaled and
     * rotated. This means that the x,y coordinate is not an absolute screen position.
     * The x,y coordinate normally define the rotation point of the object.
     * @see #_rotation
     * @see #_scale
     * @see #_width
     * @see #_height  
     */
    public int _x, _y;
    /**
     * Used to define the bounding box of an object. Used both for drawing visual
     * components such as the width and height of a button, and defining the default
     * hit zone of an object. The value may not required in some objects, such as
     * MyGUIGroup but is common in most elements.
     * @see #_width
     * @see #_height
     * @see #_x
     * @see #_y
     * @see #checkForHit()
     */
    public int _width, _height;
    /**
     * The id used to self check if the object has focus. The id is checked against
     * a value in the parent to see if it has been granted focus. The object's id is
     * commonly a reference to the index in MyGUIGroup elements[] where the object is
     * grouped with other objects. In this situation _id is not unique. However, in
     * programming more advanced grouping schemes this value may have a different role.
     * @see #hasFocus()
     * @see MyGUIGroup
     */
    public int _id; 
    /**
     * The value in degrees defining the object rotation. This value is used to transform
     * the drawing matrix and is used in updateLocalMouse(). It can be set directly or
     * by using the rotateDegrees() and rotateRadians() methods.
     * @see #rotate(float)
     * @see #rotateRadians(float)
     */
    public float _rotation;
    /**
     * Visual scale of the object. Its default value is 1.0, increasing this makes the
     * object look bigger on the screen, decreasing it makes it smaller. Negative values
     * are allowed.
     */
    public float _scale = 1.0f;
    /**
     * The visibility and active state of the object. True for visible, false for hidden.
     * When hidden the object will not receive any mouse or key events, and so is practically
     * disabled.
     * @see #_disabled  
     */
    public boolean _visible = true;
    /**
     * Used to block mouse and key events. Set to true to disable an object, false for 
     * normal operation. When disabled an object should not receive any mouse or key
     * events from its parents. It will still have its draw method called. To reflect
     * the fact that it is disabled a draw state condition should be written to resemble
     * something disabled and unoperable. MyGUIStyle contains a disabled colour which
     * can be used to replace the face, or buttonFace colour in this situation.
     * @see #_visible
     * @see MyGUIStyle#disabled 
     */
    public boolean _disabled = false;
    /**
     * Description attached to an ActionEvent generated by the component. If an object
     * generates a MyGUIActionEvent then this string will be used for the Event's
     * action command. It will be accessible by the ActionEvent.getActionCommand() method.
     * @see MyGUIActionEvent
     * @see #setActionCommand(String) 
     */
    public String _actionCommand = "";
    /**
     * Local mouse coordinate based on parent. These integer coordinates reflect the local
     * position of the mouse relative to where the object is now. They are calculated in
     * the updateLocalMouse() method by looking at the parent's value and adjusting for
     * _rotation, _scale and _x, _y translation.
     * @see #updateLocalMouse()
     */
    public int tmouseX, tmouseY;
    // Drawing logic
    /**
     * State where mouse is clicked and dragged across object. Dragged is only set to true
     * if the object has gained focus and the mouse has not yet been released. The instant
     * that a user clicks inside the bounding box of the object dragged will be set. It is
     * immediately unset on mouseRelease. The method isDragged() can be used to check this
     * state in a program. 
     * @see #isDragged()
     */
    protected boolean dragged = false;
    /**
     * State where mouse hovers over object. The most accurate and current value for hover
     * should be based off the boolean checkForHit() method. However, to minimise calls to
     * this method the hover state can be used instead. A shape can be hovered and dragged
     * at the same time.
     * @see #checkForHit()
     * @see #lastHover
     */
    protected boolean hover = false;
    /**
     * State that changes when as a mouse moved outside bounding box. Although I've not 
     * implemented this anywhere yet, lastHover detected the change between a mouse
     * hovering and not hovering. Important in conjunction with <b>dragged</b> because
     * you may want a button to only respond if the mouse is released over the button
     * as apposed to a user clicking, dragging, moving away from the button, and letting
     * go. Its a fine line, but its actually a feature of most buttons that MyGUIButton
     * neglects. 
     * @see #hover
     * @see #dragged
     * @see MyGUIButton
     */
    protected boolean lastHover = false;
    

    /**
     * Most basic class constructor.
     * <b>NOTE:</b> There is no empty constructor, all classes that extend MyGUI must have a call
     * to super(root), supplying their own path to root. This is so they have access to the
     * correct draw methods within processing.
     * @param root a direct reference to the main processing PApplet.
     */
    public MyGUIObject(PApplet root) {
        _root = root;
        _x = 0;
        _y = 0;
    }
    
    /**
     * Class constructor where the x and y coordinates can be specified.
     * @param root a direct reference to the main processing PApplet.
     * @param x    x coordinate of the object
     * @param y    y coordinate of the object
     */
    public MyGUIObject(PApplet root, int x, int y) {
        _root = root;
        // Coordinates of shape
        _x = x;
        _y = y;
    }
    
    /**
     * Returns true if the object has focus.
     * This method checks with its parent to see if this object has focus. If it doesn't,
     * then it returns false. Compatable parent objects will keep a list of their children
     * and which children have focus, this method calls _parent.getFocusIndex() and
     * compares the focus index with the id/index of this object.
     * 
     * @return returns true if the element has focus, false if it does not.
     * 
     * @see #getFocusIndex()
     * @see MyGUIGroup#getFocusIndex()
     * @see #_id
     */
    public boolean hasFocus() {
        if(_id == _parent.getFocusIndex()) { return true; }
        return false;
    }
    
    /**
     * Checks the _disabled variable and its parent. This maintains the hierarchical
     * structure where disabling a parent will also indirectly disable its children.
     * 
     * @return returns true if disabled, false if enabled (normal state).
     * 
     * @see #_disabled
     */
    public boolean isDisabled() {
        if(_disabled) { return true; }
        if(_parent != null) {
            return _parent.isDisabled();
        }
        return false;
    }
    
    /* Default responses to mouse events */
    /**
     * Returns constant state of mouse clicked compared to this object.
     * If the mouse is considered permenantly pressed or dragged. The state should only
     * be initialised after the object has gained focus.
     * 
     * @return returns true if clicked/dragged and false otherwise.
     * 
     * @see #hover
     * @see #mousePressed()
     * @see #hasFocus()
     */
    public boolean isDragged() {
        return dragged;
    }
    
    /**
     * Sets dragged to true if object has focus. This is the most basic response
     * to a mouse click. Arguably it could generate an actionEvent as well if it
     * had focus. This method can be easily overriden for custom handling of mouse
     * events, specifically the mousePressed() event. It is called by its parent every
     * time the mouse is pressed.
     * @see #mouseReleased()
     * @see #mouseDragged()
     */
    public void mousePressed() {
        if(hasFocus()) {
            dragged = true;
        }
    }
    
    /**
     * Sets dragged to false. This is the most basic response to the mouse being
     * released. If you want to do something fancy when the mouse is released then
     * feel free to override this method in your own class. It is called by its parent
     * every time the mouse is released.
     *
     */
    public void mouseReleased() {
        dragged = false;
    }
    
    /**
     * Interface method. It does nothing. When the mouse is dragged this method is called
     * by the parent object. Useful for real time dragging. MyGUIButtonDrag uses this to
     * update its _x and _y coordinates when the dragged state is set to true.
     */
    public void mouseDragged() {
        
    }
    
    /* Default responses to key events */
    /**
     * Interface method. Automatically called by the parent object when a key is pressed.
     */
    public void keyPressed(KeyEvent e) {
        
    }  
    
    /**
     * Interface method. Automatically called by the parent object when a key is released.
     */
    public void keyReleased(KeyEvent e) {
        
    }
    /**
     * Interface method. Automatically called by the parent object when an ascii key
     * is typed. e.getChar() can be used when this method is called.
     */
    public void keyTyped(KeyEvent e) {
        
    }
    
    /**
     * Code peformed every frame in the main applet happens here.
     * This method recalls drawStates() based on visibility (_visible set to true).
     * I would advise this function is not overriden. Instead, code for drawing objects
     * based on the states (_disabled, dragged, hover, hasFocus()) should be added to
     * the drawStates() method.
     * <p>
     * The method draw() contains a filter which prevents drawStates() from being called
     * if _visible is set to false. This was introduced for easy encapsulation of the
     * draw methods if any more logical conditions need be applied. This is a second stage
     * filter infact. MyGUIGroup (the standard parent object) also checks _visibility when
     * calling the draw method - other grouping objects in the future may not and so this
     * is why there is a second filter here.
     * 
     * @see #drawStates()
     * @see #_visible
     */
    public void draw() {
        if(_visible) drawStates();
    }
    
    /**
     * Interface method called by draw(). If you using the default draw() method then all
     * your draw code for objects should go in this drawStates() method. You should build
     * in drawing routines for all the standard states. For a button there are six states
     * that I have identified:
     * <ul>
     * <li>Default</li>
     * <li>Disabled</li>
     * <li>Focused Default</li>
     * <li>Hover</li>
     * <li>Focused Hover</li>
     * <li>Dragged / Depressed</li>
     * </ul>
     * There are a range of basic colours that can be used to visually identify these
     * states to the end user. See the MyGUIStyle class for more details.
     * 
     * @see MyGUIStyle
     * @see #draw()
     * @see #_disabled
     * @see #hasFocus()
     * @see #hover
     * @see #dragged
     */
    public void drawStates() {
        
    }
    
    /**
     * Sets the unique id to the specified integer.
     * This value usually matches the array entry of the parent object 'storing' it.
     * The _id is used to check whether an element has focus or not. If the ID is not
     * set it risks being considered as 0, and may accidently gain focus. If an element
     * does not have a parent then it's _id should be set to -1 (an invalid array location).
     * 
     * @param newID The new ID of the object.
     * 
     * @see #_id
     * @see #hasFocus()
     * @see #getFocusIndex()
     */
    public void setID(int newID) {
        this._id = newID;
    }
    
    /**
     * Tells the object who its parent is.
     * A new object must be told what its parent is using this method. I've done this to
     * cut down on the number of variables people have to supply in the constructor.
     * 
     * @param parent the parent MyGUIObject of this object. Usually a MyGUIGroup object.
     * 
     * @see MyGUIGroup
     */
    public void setParent(MyGUIObject parent) {
        this._parent = parent;
        this._root = parent._root;
    }
    
    /**
     * Sets the descriptive action command when an ActionEvent is generated.
     * This method allows a user to set a custom action command string, for example on
     * a button a user could set this to "Save". Later, when a MyGUIActionEvent is
     * generated this string can be compared using
     * <i>if(e.getActionCommand().equals("Save"))</i>. This is most useful when you have
     * several GUI elements that you want to execute the same piece of code by assinging
     * them the same action command. Otherwise, it might be easier to use
     * <i>if(e.getSource() == button)</i>, providing you have itemized your elements in that format.
     *  
     * @param command The string value used to identify this action command.
     * 
     * @see MyGUIActionEvent
     * @see #_actionCommand
     */
    public void setActionCommand(String command) {
        this._actionCommand = command;
    }
    
    /**
     * Sets a custom style for this object. If you want to customise a specific object then
     * you can create and modify new MyGUIStyle and apply it to an object. Applying this
     * to a group object will affect the look and style of all its children. 
     * 
     * @param style A valid MyGUIStyle object.
     * 
     * @see MyGUIStyle
     * @see #_style
     */
    public void setStyle(MyGUIStyle style) {
        this._style = style;
    }
    
    /**
     * Activates this object so it is able to receive mouse and keyboard events.
     * Its the opposite of disabled(). The method sets the _disabled attribute to false.
     * @see #disable()
     * @see #_disabled
     */
    public void enable() {
        _disabled = false;
    }
    
    /**
     * Disables this object so that it not longer receives mouse or keyboard events. 
     * Its counter method is enable. The attribute _disabled is used by MyGUIGroup to
     * block mouseEvent and keyEvent methods from reaching an object. The draw methods
     * of MyGUIObjects should reflect the disabled state by using the MyGUIStyle.disabled
     * colour, which by default is a tinted greyed version of the buttonFace colour.
     * @see #enable()
     * @see #_disabled
     */
    public void disable() {
        _disabled = true;
    }
    
    /**
     * Increases/decreases the _rotation attribute by the supplied amount. You may
     * also use guiObject._rotation += degs; since _rotation is a public variable.
     * The value is always normalized to lie within 0 < angle < 360. 
     * 
     * @param degs the value in degrees to alter the rotation by
     * 
     * @see #_rotation
     * @see #rotateRadians(float)
     * @see #normalize(float, float, float)
     */
    public void rotate(float degs) {
        _rotation += degs;
        _rotation = normalize(_rotation, 0, 360);
    }
    /**
     * Increases/decreases the _rotation attribute by the given amount. It converts
     * the supplied value from radians into degrees. The value is always normalized to
     * lie within 0 < angle < 360.
     * 
     * @param rads the value in radiands to alter the rotation by
     * 
     * @see #_rotation
     * @see #rotate(float)
     * @see #normalize(float, float, float)
     */
    public void rotateRadians(float rads) {
        _rotation += PApplet.degrees(rads);
        _rotation = normalize(_rotation, 0, 360);
    }
    
    /**
     * Normalization function that ensures a value always lies within min/max. Unlike PApplet.constrain
     * this method will loop a value, like an angle 370 degrees will change to 10 degrees. This is the main
     * intention for the method.
     * 
     * @param n the number you want to normalize
     * @param min the minimum boundary value
     * @param max the maximum boundary value
     * @return returns a normalized value that lies within min <= n <= max
     */
    protected float normalize(float n, float min, float max) { 
        float diff = max - min; 
        if(n < min) {  
            return normalize(n+diff, min, max); 
        } 
        else 
            if(n > max) { 
                return normalize(n-diff, min, max); 
            } 
            else return n; 
    } 
    
    /**
     * Defines the bounding box or 'hitzone' and returns true if the mouse is in contact.
     * The method is used to check the hover state and whether the element can gain focus.
     * This method calls updateLocalMouse() to set relative mouse coordinates tmouseX and
     * tmouseY before peforming the check. You should write your own custom method if yo
     * had an odd shaped shape that you want to calculate the position for. <p>
     * This default checkForHit methods checks the bounding box of a shape
     * (-width/2, _y-width/2, _width/2, _height/2) taking into account when rotatation and scale.
     * 
     * @see #_width
     * @see #_height
     * @see #updateLocalMouse()
     */
    public boolean checkForHit() {
        // Because this object supports rotation the checkForHit method must also rotate its mouse coordinates
        // I have done this by offsetting by the rotation point (_x-_width/2, _y-_height/2) and then rotating
        // this value back so it can be checked against the _width and _height
        updateLocalMouse();
        if(tmouseX >= (-_width/2) * _scale && tmouseY >= (-_height/2) * _scale
        && tmouseX <= (+_width/2) * _scale && tmouseY <= (+_height/2) * _scale)
        {
            lastHover = true;
            return true;
        }
        lastHover = false;
        return false;
    }
    
    /**
     * Calculates the local relative mouse coordinates. By local this means the tmouseX
     * and tmouseY coordinates are set relative to the _x, _y position of this object.
     * These are then used in public boolean checkForHit();
     * 
     * @see #checkForHit()
     */
    public void updateLocalMouse() {
        // This code was taken from MyGUIObject.checkForHit
        // it calculates the apparent (non rotated) mouse coordinates
        if(_rotation != 0) {
            float cosval = _root.cos(-PApplet.radians(_rotation));
            float sinval = _root.sin(-PApplet.radians(_rotation));
            int rotationPointX = (_parent.tmouseX - _x);
            int rotationPointY = (_parent.tmouseY - _y);
            tmouseX = PApplet.round(rotationPointX * cosval - rotationPointY * sinval * _parent._scale);
            tmouseY = PApplet.round(rotationPointX * sinval + rotationPointY * cosval * _parent._scale);
        } 
        else {
            // No rotation, just use the values
            tmouseX = PApplet.round((_parent.tmouseX - _x) * _parent._scale);
            tmouseY = PApplet.round((_parent.tmouseY - _y) * _parent._scale);
        }
    }
    
    /* Get Methods */
    
    /**
     * Returns the currently assigned style. Each MyGUIObject takes its own style
     * object from its parent. This means that if you modify it for a single object
     * you will probably affect the whole hierarchy. To change the specific style for
     * a single object try creating a new MyGUIStyle variable and then use
     * setStyle(MyGUIStyle)
     * 
     * @return returns a reference to the currently assigned MyGUIStyle style
     * 
     * @see MyGUIStyle
     * @see #setStyle(MyGUIStyle)
     * @see #_style
     */
    public MyGUIStyle getStyle() {
        return _style;
    }
    
    /* Dud methods */
    /**
     * Returns -1, usually an invalid index.
     * Not all MyGUIObject's are designed to track children. The getFocusIndex is required
     * so that children can check with their parent to see if they are selected.
     * Classes extending MyGUIObject will have to override this method if they intend to
     * support tracking the focus of their children - as well as having other capabilities.
     * 
     * @return returns -1 always. Pretty boring really.
     * 
     * @see #hasFocus()
     * @see #_id
     */
    public int getFocusIndex() {
        return -1;
    }
    
    /**
     * Returns false, not usually used.
     * Setting the focus index can cause problems within a GUI structure. 
     * This method is put in place so that groups objects that receive focus
     * can tell the parent object that they have gained focus and maintain the 
     * event tree, so that commands such as keyPressed are passed to the next object.
     * @param index
     * @return  false if the index cannot be set, true if it has been changed.
     * 
     * @see #MyGUIGroup.setFocusIndex(int index)
     */
    public boolean setFocusIndex(int index) {
        return false;
    }
}


